/************************************************************************
This file is part of ts3300 package for the simpleRTJ virtual machine.

This file is covered by the GNU GPL with the following exception:
  As a special exception, the copyright holders of this library give you permission
  to link this library with independent modules to produce an executable, regardless
  of the license terms of these independent modules, and to copy and distribute the
  resulting executable under terms of your choice, provided that you also meet, for
  each linked independent module, the terms and conditions of the license of that
  module. An independent module is a module which is not derived from or based on
  this library. If you modify this library, you may extend this exception to your
  version of the library, but you are not obligated to do so. If you do not wish
  to do so, delete this exception statement from your version.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL RTJ COMPUTING BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Copyright (c) 2000-2006 RTJ Computing Pty. Ltd. All rights reserved.
***********************************************************************/
package lpc2xxx.comm;


/**
 * Class providing access to two LPC21xx' serial ports: UART0, UART1.<br>
 * Available serial ports are COM1 and COM2. Serial ports can be configured
 * to work in various communication modes.<br>
 * Note: This class doesn't fully implement all of the COM functionality. 
 * Add your own code to implement additional features.
 */
public class UART
{
    // available serial ports
    public final static int UART0 = 0;
    public final static int UART1 = 1;

    public final static int PAR_NONE    = 0x00;
    public final static int PAR_ODD     = 0x08;
    public final static int PAR_EVEN    = 0x18;
    public final static int PAR_FORCE1  = 0x28;
    public final static int PAR_FORCE0  = 0x38;

    public final static int STOP_1      = 0x00;
    public final static int STOP_2      = 0x04;

    public final static int BITS_5      = 0;
    public final static int BITS_6      = 1;
    public final static int BITS_7      = 2;
    public final static int BITS_8      = 3;

    // serial port status information
    public final static int STAT_FIFOERR    = 0x80;
    public final static int STAT_TXEMPTY    = 0x40;
    public final static int STAT_TXNOTFULL  = 0x20;
    public final static int STAT_LINEBREAK  = 0x10;
    public final static int STAT_FRAMEERR   = 0x08;
    public final static int STAT_PARITYERR  = 0x04;
    public final static int STAT_OVERRUN    = 0x02;
    public final static int STAT_DATAAVAIL  = 0x01;


    private int uart;
    private boolean isOpen = false;
    private int timeout = 1000;

    private final static String notOpenedMsg = "Port not opened";

    /**
     * Serial port constructor. It takes one argument specifying the port
     * number. 
     * @param uart specifies serial port UART0 or UART1
     * @exception Exception is thrown if invalid port number is specified
     */
    public UART(int uart) throws Exception
    {
        if (uart != UART0 && uart != UART1)
            throw new Exception("Invalid port No.");

        this.uart = uart;
    }

    /**
     * Opens the serial port if not already opened.<br>
     * For baud, parity, bits and stop parameters use the constants provided
     * in this class to ensure proper operation.
     * @param baud baud rate of the serial port, ie. 1200, 2400, 4800, 9600, 19200, 38400, 57600 or 115200
     * @param parity pariy setting for the serial port, use PAR_xxx constants
     * @param bits select number of bits for transmission, use BITS_x constants
     * @param stop number of stop bits, use STOP_x constants
     * @return true if port is successfully opened, otherwise false is returned.
     */
    public boolean open(int baud, int parity, int bits, int stop)
    {
        if (!isOpen)
            isOpen = open0(uart, baud, bits, parity, stop);

        return isOpen;
    }

    /**
     * Closes the serial port (if opened).
     */
    public void close()
    {
        if (isOpen)
            close0(uart);
    }

    /**
     * Clears the serial port receive buffer.
     */
    public void purge()
    {
        if (isOpen)
            purge0(uart);
    }

    /**
     * Reads the serial port status register.<br>
     * Use the provided constants to determine the actual status information.
     * @return serial port status value
     * @exception Exception is thrown if port is not opened
     */
    public int getStatus() throws Exception
    {
        if (!isOpen)
            throw new Exception(notOpenedMsg);
        return getStatus0(uart);
    }

    /** 
     * Returns true if the UART's FIFO error occured 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isFifoError() throws Exception
    {
        return (getStatus() & STAT_FIFOERR) > 0;
    }

    /** 
     * Returns true if the transmitter buffer is empty 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isTxEmpty() throws Exception
    {
        return (getStatus() & STAT_TXEMPTY) > 0;
    }

    /** 
     * Returns true if the transmitter buffer is not full 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isTxNotFull() throws Exception
    {
        return (getStatus() & STAT_TXNOTFULL) > 0;
    }

    /** 
     * Returns true line break has been detected 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isLineBreak() throws Exception
    {
        return (getStatus() & STAT_LINEBREAK) > 0;
    }

    /** 
     * Returns true framing error has been detected 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isFrameError() throws Exception
    {
        return (getStatus() & STAT_FRAMEERR) > 0;
    }

    /** 
     * Returns true parity error has been detected 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isParityError() throws Exception
    {
        return (getStatus() & STAT_PARITYERR) > 0;
    }

    /** 
     * Returns true if overrun occured on receiver 
     * @exception Exception is thrown if port is not opened
     */
    public boolean isOverrun() throws Exception
    {
        return (getStatus() & STAT_OVERRUN) > 0;
    }

    /** 
     * Checks is there is data available on the serial port.
     * @return true if data is available in the Rx buffer.
     */
    public boolean isDataAvail()
    {
        return dataAvail(uart);
    }

    /**
     * Sets the timeout value for all read operations. Default value upon UART 
     * class creation is 1000 ms.
     * @param ms number of milliseconds to wait for data to arrive.
     */
    public void setTimeout(int ms)
    {
        if (ms > 0)
            timeout = ms;
    }

    /**
     * Reads one byte from the serial port.<br>
     * If the data is not available within the timeout period an exception is thrown.
     * @return one byte from the serial port receive buffer.
     * @exception Exception is thrown if port is not opened
     * @exception TimeoutException is thrown if data is not available within specified time period.
     */
    public synchronized byte read() throws Exception, TimeoutException
    {
        if (!isOpen)
            throw new Exception(notOpenedMsg);
        int tout = System.currentTimeMillis() + timeout;
        while (true)
        {
            if (dataAvail(uart))
                return read0(uart);
            if (System.currentTimeMillis() > tout)
                throw new TimeoutException();
            Thread.yield();
        }
    }

    /**
     * Reads count bytes from the serial port into destination array at specified offset.<br>
     * If the specified number of bytes is not available within the timeout period
     * an exception is thrown.
     * @param dest destination byte array
     * @param offs starting offset to destination array
     * @param count number of bytes to read from serial port
     * @exception Exception is thrown if port is not opened
     * @exception NullException is thrown if <code>dest</code> object is null
     * @exception IndexOutOfBoundsException is thrown there is not enough room in the detination
     * @exception TimeoutException is thrown if data is not available within specified time period.
     */
    public synchronized void read(byte[] dest, int offs, int count) throws Exception, TimeoutException
    {
        if (!isOpen)
            throw new Exception(notOpenedMsg);
        if (dest == null)
            throw new NullPointerException();
        if (dest.length < offs + count)
            throw new IndexOutOfBoundsException();
        int tout = System.currentTimeMillis() + timeout;
        while (count > 0)
        {
            if (dataAvail(uart)) 
            {
                dest[offs++] = read0(uart);
                count--;
            }
            if (System.currentTimeMillis() > tout)
                throw new TimeoutException();
            Thread.yield();
        }
    }

    /**
     * Writes one byte to the serial port. If there is no room in the transmit
     * buffer within the timeout perion an exception is thrown.
     * @param value byte value to write to the serial output buffer
     * @exception Exception is thrown if port is not opened
     * @exception TimeoutException is thrown if transmit buffer is not empty within specified time period.
     */
    public synchronized void write(byte value) throws Exception, TimeoutException
    {
        if (!isOpen)
            return;
        int tout = System.currentTimeMillis() + timeout;
        while (true)
        {
            if (isTxNotFull())
            {
                write0(uart, value);
                return;
            }
            if (System.currentTimeMillis() > tout)
                throw new TimeoutException();
            Thread.yield();
        }
    }

    /**
     * Writes count bytes to the serial port from source array at specified offset.<br>
     * If the specified number of bytes is not written within the timeout period
     * an exception is thrown.
     * @param source source byte array
     * @param offs offset to the first byte in the source array
     * @param count number of bytes to transmit
     * @exception Exception is thrown if port is not opened
     * @exception NullException is thrown if <code>source</code> object is null
     * @exception IndexOutOfBoundsException is thrown if reading from the outside of source array bounds occur
     * @exception TimeoutException is thrown if transmit buffer is not empty within specified time period.
     */
    public synchronized void write(byte[] source, int offs, int count) throws Exception, TimeoutException
    {
        if (source == null)
            throw new NullPointerException();
        if (source.length < offs + count)
            throw new IndexOutOfBoundsException();
        int tout = System.currentTimeMillis() + timeout;
        while (count > 0)
        {
            if (isTxNotFull())
            {
                write0(uart, source[offs++]);
                count--;
            }
            if (System.currentTimeMillis() > tout)
                throw new TimeoutException();
            Thread.yield();
        }
    }

    /**
     * Writes String to the serial port.<br>
     * If the string is not written within the timeout period an exception is thrown.
     * @param source String object 
     * @exception Exception is thrown if port is not opened
     * @exception NullException is thrown if <code>source</code> object is null
     * @exception TimeoutException is thrown if transmit buffer is not empty within specified time period.
     */
    public void write(String source) throws Exception, TimeoutException
    {
        if (source == null)
            throw new NullPointerException();
        byte barr[] = source.getBytes();
        write(barr, 0, barr.length);
    }

    /** Opens the specified serial uart */
    private native boolean open0(int uart, int baud, int bits, int parity, int stop);
    /** Closes the specified serial uart */
    private native void close0(int uart);
    /** Purges Rx and Tx buffers on specified uart */
    private native void purge0(int uart);
    /** Returns the status of the specified uart */
    private native int getStatus0(int uart);
    /** Checks if a character is available for reading. */
    private native boolean dataAvail(int uart);
    /** Reads one byte from serial uart. Waits if not available. */
    private native byte read0(int uart);
    /** Writes one byte to the serial uart. */
    private native void write0(int uart, byte value);
}
